// Copyright 2010 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.api.adwords.lib;

import org.apache.axis.client.Stub;

import java.lang.reflect.InvocationTargetException;

import javax.xml.rpc.ServiceException;

/**
 * For each active version of the AdWords API, there should exist a sub-class
 * which implements the abstract class.
 *
 * @author api.arogal@gmail.com (Adam Rogal)
 */
public abstract class AdWordsVersion {
  /**
   * The {@code AdWordsVersion} for v13.
   */
  public static final AdWordsVersion V13 = new AdWordsVersionV13();

  protected final String version;
  protected final String namespacePrefix;
  protected final String packagePrefix;
  protected final String sandboxUrl;
  protected final String prodUrl;

  /**
   * @param version the version name i.e. v13, v200906, or v200909
   * @param namespacePrefix the namespace prefix
   * @param packagePrefix the package prefix such that the package
   *     {@code packagPrefix + "." + version} contains all
   *     code for that version
   * @param sandboxUrl the sandbox URL
   * @param prodUrl the production URL
   */
  protected AdWordsVersion(String version, String namespacePrefix, String packagePrefix,
      String sandboxUrl, String prodUrl) {
    this.version = version;
    this.namespacePrefix = namespacePrefix;
    this.packagePrefix = packagePrefix;
    this.sandboxUrl = sandboxUrl;
    this.prodUrl = prodUrl;
  }

  /**
   * Gets the version string.
   *
   * @return the version
   */
  public String getVersion() {
    return version;
  }

  /**
   * Gets the namespace prefix.
   *
   * @return the namespacePrefix
   */
  public String getNamespace() {
    return namespacePrefix;
  }

  /**
   * Gets the package prefix which can be used to instantiate the Axis Proxy
   * class.
   *
   * @return the packagePrefix
   */
  public String getPackagePrefix() {
    return packagePrefix;
  }

  /**
   * Gets the URL used for the endpoint address to the sandbox environment
   *
   * @return the sandboxUrl
   */
  public String getSandboxUrl() {
    return sandboxUrl;
  }

  /**
   * Gets the URL used for the endpoint address to the production environment
   *
   * @return the prodUrl
   */
  public String getProductionUrl() {
    return prodUrl;
  }

  /**
   * Returns the version string
   */
  @Override
  public String toString() {
    return version;
  }

  /**
   * Sets the headers for the service designated by {@code stub}.
   *
   * @param stub the stub which headers need populating
   * @param user the user to populate the stub headers with
   * @param validateOnly {@code true} if the call should only be validated. Only
   *     applicable in v200909.
   * @throws ServiceException if headers could not be set either because the
   *     soap header object could not be instantiated and configured correctly,
   *     or because the {@code AdWordsUser} could not be authenticated.
   */
  public abstract void setHeaders(Stub stub, AdWordsUser user, boolean validateOnly)
      throws ServiceException;

  /**
   * The v13 version class.
   */
  private static class AdWordsVersionV13 extends AdWordsVersion {
    /**
     * Constructor for v13 version.
     */
    protected AdWordsVersionV13() {
      super("v13", "https://adwords.google.com/api/adwords/", "com.google.api.adwords.",
          "https://sandbox.google.com", "https://adwords.google.com");
    }

    /**
     * See {@link AdWordsVersion#setHeaders(Stub, AdWordsUser, boolean)}.
     */
    @Override
    public void setHeaders(Stub stub, AdWordsUser user, boolean validateOnly) {
      String namespace = namespacePrefix + version;
      stub.setHeader(namespace, "email", user.getEmail());
      stub.setHeader(namespace, "password", user.getPassword());
      stub.setHeader(namespace, "developerToken", user.getDeveloperToken());
      stub.setHeader(namespace, "applicationToken", user.getApplicationToken());
      stub.setHeader(namespace, "useragent", user.getClientLibraryIdentifier());

      if (user.getClientEmail() != null) {
        stub.setHeader(namespace, "clientEmail", user.getClientEmail());
      } else if (user.getClientCustomerId() != null) {
        stub.setHeader(namespace, "clientCustomerId", user.getClientCustomerId());
      }
    }
  }

  /**
   * The v200909 version class.
   */
  public static class AdWordsVersionV200909 extends AdWordsVersion {
    private final String serviceGroup;
    /**
     * Constructor for v200909 version.
     */
    public AdWordsVersionV200909(String serviceGroup) {
      super("v200909", "https://adwords.google.com/api/adwords",
          "com.google.api.adwords.", "https://adwords-sandbox.google.com",
          "https://adwords.google.com");
      this.serviceGroup = serviceGroup;
    }

     /**
      * See {@link AdWordsVersion#setHeaders(Stub, AdWordsUser, boolean)}.
      */
     @Override
     public void setHeaders(Stub stub, AdWordsUser user, boolean validateOnly)
         throws ServiceException {
       try {
         if (user.getRegisteredAuthToken() == null) {
           user.setAuthToken(AuthToken.getAuthTokenAsString(user));
         }

         stub.setHeader(namespacePrefix + "/" + serviceGroup + "/" + version, "RequestHeader",
             Class.forName(packagePrefix + this.toString() + ".cm." + "SoapHeader")
                 .getConstructor(String.class, String.class,
                     String.class, String.class, String.class, String.class, Boolean.class)
                 .newInstance(user.getApplicationToken(), user.getRegisteredAuthToken(),
                     user.getClientCustomerId(),
                     user.getClientEmail(), user.getDeveloperToken(),
                     user.getClientLibraryIdentifier(), validateOnly));
       } catch (IllegalStateException e) {
         throw new ServiceException("Could not get ClientLogin token for user: " + e.getMessage(), e);
       } catch (AuthTokenException e) {
         throw new ServiceException("Could not get ClientLogin token for user: " + e.getMessage(), e);
       } catch (InstantiationException e) {
         throw new ServiceException("Could not create service class. Check classpath.", e);
       } catch (IllegalAccessException e) {
         throw new ServiceException(e.getMessage(), e);
       } catch (ClassNotFoundException e) {
         throw new ServiceException("Could not create service class. Check classpath.", e);
       } catch (IllegalArgumentException e) {
         throw new ServiceException(e.getMessage(), e);
       } catch (SecurityException e) {
         throw new ServiceException(e.getMessage(), e);
       } catch (InvocationTargetException e) {
         throw new ServiceException(e.getMessage(), e.getCause());
       } catch (NoSuchMethodException e) {
         throw new ServiceException("Could not create service class. "
             + "Check that the correct version of AXIS is being used.", e);
       }
     }
  }

  /**
   * The v201003 version class.
   */
  public static class AdWordsVersionV201003 extends AdWordsVersion {
    private final String serviceGroup;
    /**
     * Constructor for v201003 version.
     */
    public AdWordsVersionV201003(String serviceGroup) {
      super("v201003", "https://adwords.google.com/api/adwords",
          "com.google.api.adwords.", "https://adwords-sandbox.google.com",
          "https://adwords.google.com");
      this.serviceGroup = serviceGroup;
    }

     /**
      * See {@link AdWordsVersion#setHeaders(Stub, AdWordsUser, boolean)}.
      */
     @Override
     public void setHeaders(Stub stub, AdWordsUser user, boolean validateOnly)
         throws ServiceException {
       try {
         if (user.getRegisteredAuthToken() == null) {
           user.setAuthToken(AuthToken.getAuthTokenAsString(user));
         }

         stub.setHeader(namespacePrefix + "/" + serviceGroup + "/" + version, "RequestHeader",
             Class.forName(packagePrefix + this.toString() + ".cm." + "SoapHeader")
                 .getConstructor(String.class,
                     String.class, String.class, String.class, String.class, Boolean.class)
                 .newInstance(user.getRegisteredAuthToken(),
                     user.getClientCustomerId(),
                     user.getClientEmail(), user.getDeveloperToken(),
                     user.getClientLibraryIdentifier(), validateOnly));
       } catch (IllegalStateException e) {
         throw new ServiceException("Could not get ClientLogin token for user: " + e.getMessage(), e);
       } catch (AuthTokenException e) {
         throw new ServiceException("Could not get ClientLogin token for user: " + e.getMessage(), e);
       } catch (InstantiationException e) {
         throw new ServiceException("Could not create service class. Check classpath.", e);
       } catch (IllegalAccessException e) {
         throw new ServiceException(e.getMessage(), e);
       } catch (ClassNotFoundException e) {
         throw new ServiceException("Could not create service class. Check classpath.", e);
       } catch (IllegalArgumentException e) {
         throw new ServiceException(e.getMessage(), e);
       } catch (SecurityException e) {
         throw new ServiceException(e.getMessage(), e);
       } catch (InvocationTargetException e) {
         throw new ServiceException(e.getMessage(), e.getCause());
       } catch (NoSuchMethodException e) {
         throw new ServiceException("Could not create service class. "
             + "Check that the correct version of AXIS is being used.", e);
       }
     }
  }

  /**
   * The v201008 version class.
   */
  public static class AdWordsVersionV201008 extends AdWordsVersion {
    private final String serviceGroup;

    /**
     * Constructor for v201008 version.
     */
    public AdWordsVersionV201008(String serviceGroup) {
      super("v201008", "https://adwords.google.com/api/adwords", "com.google.api.adwords.",
          "https://adwords-sandbox.google.com", "https://adwords.google.com");
      this.serviceGroup = serviceGroup;
    }

    /**
     * See {@link AdWordsVersion#setHeaders(Stub, AdWordsUser, boolean)}.
     */
    @Override
    public void setHeaders(Stub stub, AdWordsUser user, boolean validateOnly)
        throws ServiceException {
      try {
        if (user.getRegisteredAuthToken() == null) {
          user.setAuthToken(AuthToken.getAuthTokenAsString(user));
        }

        stub.setHeader(namespacePrefix + "/" + serviceGroup + "/" + version, "RequestHeader", Class
            .forName(packagePrefix + this.toString() + ".cm." + "SoapHeader").getConstructor(
                String.class, String.class, String.class, String.class, String.class,
                Boolean.class, Boolean.class).newInstance(user.getRegisteredAuthToken(),
                user.getClientCustomerId(), user.getClientEmail(), user.getDeveloperToken(),
                user.getClientLibraryIdentifier(), validateOnly, user.isUsingPartialFailure()));
      } catch (IllegalStateException e) {
        throw new ServiceException("Could not get ClientLogin token for user: " + e.getMessage(), e);
      } catch (AuthTokenException e) {
        throw new ServiceException("Could not get ClientLogin token for user: " + e.getMessage(), e);
      } catch (InstantiationException e) {
        throw new ServiceException("Could not create service class. Check classpath.", e);
      } catch (IllegalAccessException e) {
        throw new ServiceException(e.getMessage(), e);
      } catch (ClassNotFoundException e) {
        throw new ServiceException("Could not create service class. Check classpath.", e);
      } catch (IllegalArgumentException e) {
        throw new ServiceException(e.getMessage(), e);
      } catch (SecurityException e) {
        throw new ServiceException(e.getMessage(), e);
      } catch (InvocationTargetException e) {
        throw new ServiceException(e.getMessage(), e.getCause());
      } catch (NoSuchMethodException e) {
        throw new ServiceException("Could not create service class. "
            + "Check that the correct version of AXIS is being used.", e);
      }
    }
  }

  /**
   * The v201101 version class.
   */
  public static class AdWordsVersionV201101 extends AdWordsVersion {
    private final String serviceGroup;

    /**
     * Constructor for v201101 version.
     */
    public AdWordsVersionV201101(String serviceGroup) {
      super("v201101", "https://adwords.google.com/api/adwords", "com.google.api.adwords.",
          "https://adwords-sandbox.google.com", "https://adwords.google.com");
      this.serviceGroup = serviceGroup;
    }

    /**
     * See {@link AdWordsVersion#setHeaders(Stub, AdWordsUser, boolean)}.
     */
    @Override
    public void setHeaders(Stub stub, AdWordsUser user, boolean validateOnly)
        throws ServiceException {
      try {
        if (user.getRegisteredAuthToken() == null) {
          user.setAuthToken(new AuthToken(user.getEmail(), user.getPassword()).getAuthToken());
        }

        stub.setHeader(namespacePrefix + "/" + serviceGroup + "/" + version, "RequestHeader", Class
            .forName(packagePrefix + this.toString() + ".cm." + "SoapHeader").getConstructor(
                String.class, String.class, String.class, String.class, String.class,
                Boolean.class, Boolean.class).newInstance(user.getRegisteredAuthToken(),
                user.getClientCustomerId(), user.getClientEmail(), user.getDeveloperToken(),
                user.getClientLibraryIdentifier(), validateOnly, user.isUsingPartialFailure()));
      } catch (IllegalStateException e) {
        throw new ServiceException("Could not get ClientLogin token for user: " + e.getMessage(), e);
      } catch (AuthTokenException e) {
        throw new ServiceException("Could not get ClientLogin token for user: " + e.getMessage(), e);
      } catch (InstantiationException e) {
        throw new ServiceException("Could not create service class. Check classpath.", e);
      } catch (IllegalAccessException e) {
        throw new ServiceException(e.getMessage(), e);
      } catch (ClassNotFoundException e) {
        throw new ServiceException("Could not create service class. Check classpath.", e);
      } catch (IllegalArgumentException e) {
        throw new ServiceException(e.getMessage(), e);
      } catch (SecurityException e) {
        throw new ServiceException(e.getMessage(), e);
      } catch (InvocationTargetException e) {
        throw new ServiceException(e.getMessage(), e.getCause());
      } catch (NoSuchMethodException e) {
        throw new ServiceException("Could not create service class. "
            + "Check that the correct version of AXIS is being used.", e);
      }
    }
  }
}
